package ycl.security.handler;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import ycl.common.constants.ExceptionConstant;
import ycl.common.constants.RedisConstant;
import ycl.security.entity.SecurityUser;
import ycl.security.service.LoginService;
import ycl.security.service.UserDetailsService;
import ycl.security.utils.JWTUtil;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 认证用户提供者
 *
 * @author ycl
 * @date 2022-03-10 13:47:02
 */
@Slf4j
@Component
public class AuthProvider extends AbstractUserDetailsAuthenticationProvider {

	@Resource
	private LoginService loginService;
	@Resource
	private StringRedisTemplate stringRedisTemplate;
	@Resource
	private PasswordEncoder passwordEncoder;
	@Resource
	private UserDetailsService userDetailsService;

	/**
	 * 附加检查
	 *
	 * @param userDetails    用户信息
	 * @param authentication 认证信息
	 * @throws AuthenticationException ecp
	 */
	@Override
	protected void additionalAuthenticationChecks(
			UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		//密码错时记录错误次数
		int count = getWrongCount(userDetails.getUsername());
		//超出登录次数上限就抛出上锁异常
		if (count >= RedisConstant.LOGIN_FAIL_COUNT)
			throw new BadCredentialsException(ExceptionConstant.USER_LOCK);
	}


	private int getWrongCount(String username) {
		Long c = stringRedisTemplate.opsForValue()
				.increment(RedisConstant.getUserLoginFailKey(username));
		if (c != null && c >= RedisConstant.LOGIN_FAIL_COUNT) {
			stringRedisTemplate.opsForValue().set(
					RedisConstant.getUserLockKey(username),
					"1",
					RedisConstant.LOCK_MINUTE,
					TimeUnit.MINUTES);
		}
		return c == null ? 0 : Math.toIntExact(c);
	}


	/**
	 * 检索用户
	 *
	 * @param username       用户名
	 * @param authentication 认证标识
	 * @return 用户
	 * @throws AuthenticationException ecp
	 */
	@Override
	protected UserDetails retrieveUser(
			String username,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		log.info("UserDetailsAuthProviderHandler . retrieveUser . 用户检索");
		String lock = stringRedisTemplate.opsForValue().get(RedisConstant.getUserLockKey(username));
		if (!StringUtils.isEmpty(lock))
			throw new BadCredentialsException(ExceptionConstant.USER_LOCK);

		SecurityUser user = (SecurityUser) userDetailsService.loadUserByUsername(username);
		String password = authentication.getCredentials().toString();
		if (!passwordEncoder.matches(password, user.getPassword())) {
			additionalAuthenticationChecks(user, authentication);
			throw new BadCredentialsException(ExceptionConstant.USER_ACCOUNT_PASSWORD_WRONG);
		}

		String token = JWTUtil.createToken(username, user.getAuthorities());
		user.setToken(token);
		return user;
	}
}
